package cn.bossfriday.common.utils;

import cn.bossfriday.common.exception.ServiceRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
 * HttpUtils
 *
 * @author chenx
 */
public class HttpUtils {

    private static final String HTTP = "http";
    private static final String HTTPS = "https";
    private static SSLConnectionSocketFactory sslConnectionSocketFactory = null;
    private static SSLContextBuilder builder = null;

    private HttpUtils() {
        // just a private constructor
    }

    static {
        try {
            builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, (TrustStrategy) (x509Certificates, s) -> true);
            
            // Java 11默认禁用了SSLv3及TLS 1.0、TLS 1.1（这些都被认为是不安全协议）
            sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build(),
                    new String[]{"TLSv1.2", "TLSv1.3"},
                    null,
                    NoopHostnameVerifier.INSTANCE);
        } catch (Exception e) {
            throw new ServiceRuntimeException(e.getMessage());
        }
    }

    /**
     * getPoolingHttpClient
     *
     * @param url
     * @return
     */
    public static CloseableHttpClient getPoolingHttpClient(String url) {
        return getPoolingHttpClient(isHttps(url));
    }

    /**
     * getPoolingHttpClient
     *
     * @param isHttps
     * @return
     */
    public static CloseableHttpClient getPoolingHttpClient(boolean isHttps) {
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register(HTTP, PlainConnectionSocketFactory.getSocketFactory())
                .register(HTTPS, sslConnectionSocketFactory)
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connectionManager.setMaxTotal(5000);
        connectionManager.setDefaultMaxPerRoute(5000);

        SocketConfig socketConfig = SocketConfig.custom()
                .setTcpNoDelay(true)
                .setSoReuseAddress(true)
                .setSoLinger(60)
                .setSoTimeout(300 * 1000)
                .setSoKeepAlive(true)
                .build();
        connectionManager.setDefaultSocketConfig(socketConfig);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(10000)
                .setConnectionRequestTimeout(10000)
                .setSocketTimeout(300 * 1000)
                .build();

        HttpRequestRetryHandler requestRetryHandler = new DefaultHttpRequestRetryHandler(0, false);

        ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (httpResponse, httpContext) -> 60 * 1000;

        if (isHttps) {
            return HttpClients.custom()
                    .setSSLSocketFactory(sslConnectionSocketFactory)
                    .setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(requestConfig)
                    .setRetryHandler(requestRetryHandler)
                    .setKeepAliveStrategy(connectionKeepAliveStrategy)
                    .build();
        }

        return HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(requestRetryHandler)
                .setKeepAliveStrategy(connectionKeepAliveStrategy)
                .build();
    }

    /**
     * getUrl
     */
    public static String getUrl(String url, boolean isHttps) {
        if (isHttps) {
            return HTTPS + "://" + url;
        }

        return HTTP + "://" + url;
    }

    /**
     * @param serverRootUrl
     * @param pathUrl
     * @return
     */
    public static String combineUrl(String serverRootUrl, String pathUrl) {
        if (StringUtils.isNotEmpty(serverRootUrl)) {
            serverRootUrl = serverRootUrl.endsWith("/") ? serverRootUrl : serverRootUrl + "/";
        }

        if (StringUtils.isNotEmpty(pathUrl)) {
            pathUrl = pathUrl.startsWith("/") ? pathUrl.substring(1) : pathUrl;
        }

        return serverRootUrl + pathUrl;
    }

    /**
     * getHostWithPort
     *
     * @param strUrl
     * @return
     * @throws MalformedURLException
     */
    public static String getHostWithPort(String strUrl) {
        try {
            URL url = new URL(strUrl);
            int port = url.getPort();

            return port == -1 ? url.getHost() : url.getHost() + ":" + port;
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("url is invalid!");
        }
    }

    /**
     * getQueryString
     *
     * @param uri
     * @return
     */
    public static String getQueryString(URI uri) {
        if (uri.getQuery() == null || uri.getQuery().isEmpty()) {

            return "";
        }

        return Arrays.stream(uri.getQuery().split("&"))
                .map(param -> {
                    try {
                        String[] kv = param.split("=", 2);
                        String key = URLEncoder.encode(kv[0], StandardCharsets.UTF_8.toString());
                        String value = kv.length > 1 ? URLEncoder.encode(kv[1], StandardCharsets.UTF_8.toString()) : "";

                        return key + "=" + value;
                    } catch (Exception ex) {
                        return "";
                    }
                })
                .sorted()
                .collect(Collectors.joining("&"));
    }

    /**
     * isHttps
     *
     * @param url
     * @return
     */
    public static boolean isHttps(String url) {
        try {
            return HTTPS.equalsIgnoreCase(new URL(url).getProtocol());
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("url is invalid!");
        }
    }

    /**
     * releaseAndClose
     *
     * @param httpClient
     * @param httpRequest
     */
    public static void releaseAndClose(CloseableHttpClient httpClient, HttpRequestBase httpRequest) {
        try {
            if (httpRequest != null) {
                httpRequest.releaseConnection();
            }

            if (httpClient != null) {
                httpClient.close();
            }
        } catch (Exception ex) {
            throw new ServiceRuntimeException("HttpUtils.releaseAndClose() error!");
        }
    }

    /**
     * printHttpRequest
     *
     * @param httpRequest
     * @return
     */
    public static String printHttpRequest(HttpRequestBase httpRequest) {
        StringBuilder sb = new StringBuilder();
        if (Objects.isNull(httpRequest)) {
            return sb.toString();
        }

        // request
        sb.append(httpRequest.getMethod())
                .append(" ")
                .append(httpRequest.getURI())
                .append("\n");

        // head
        for (Header header : httpRequest.getAllHeaders()) {
            sb.append(header.getName())
                    .append(": ")
                    .append(header.getValue())
                    .append("\n");
        }

        // body
        if (httpRequest instanceof HttpEntityEnclosingRequestBase) {
            HttpEntityEnclosingRequestBase entityRequest = (HttpEntityEnclosingRequestBase) httpRequest;
            HttpEntity entity = entityRequest.getEntity();
            if (Objects.isNull(entity)) {
                return sb.toString();
            }

            sb.append("\n");
            String contentType = entity.getContentType() != null ? entity.getContentType().getValue() : "";
            boolean isPrintable = contentType.contains("application/json")
                    || contentType.contains("application/xml")
                    || contentType.contains("application/x-www-form-urlencoded");
            if (!isPrintable) {
                sb.append("<binary content...>");
                return sb.toString();
            }

            try (InputStream inputStream = entity.getContent();
                 Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
                String body = scanner.hasNext() ? scanner.next() : "";
                sb.append(body);
            } catch (IOException e) {
                sb.append("<error reading body: ").append(e.getMessage()).append(">");
            }
        }

        return sb.toString();
    }
}
